/**
 * 
 * Copyright 2009, 2010 Mike Jamer [email: mikejamer@gmail.com ]
 * 
 * This File is part of Moose IRC
 * 
 * MooseIRC is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * MooseIRC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with MooseIRC.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace moose.irc {
    /// <summary>
    /// Wrapps Socket, suitable for IRC Session
    /// </summary>
    [CLSCompliant(true)]
    public sealed class IrcConnection : object, IDisposable {
        /// <summary>
        /// Connection to IRC Server
        /// </summary>
        private Socket m_socket;

        /// <summary>
        /// Used to buffer incomming responses
        /// </summary>
        private StringBuilder m_response_buffer;

        /// <summary>
        /// Initialize IrcConnection
        /// </summary>
        internal IrcConnection()
            : base() {
            Response += onResponse;
            return;
        }

        /// <summary>
        /// Indicates whethere we're connected to teh server or not
        /// </summary>
        public bool Connected {
            get {
                return m_socket.Connected;
            }
        }

        /// <summary>
        /// Signal Indicating a response from the server
        /// </summary>
        public event SimpleResponseHandler Response;

        /// <summary>
        /// Connect to the server 
        /// </summary>
        /// <param name="server_endpoint">
        /// A <see cref="EndPoint"/>
        /// </param>
        /// <returns>
        /// A <see cref="System.Boolean"/>
        /// </returns>
        public bool connect(EndPoint server_endpoint) {
            if (m_socket != null) disconnect();

            m_socket = create_socket();
            m_socket.Connect(server_endpoint);

            if (m_socket.Connected) {
                m_response_buffer = new StringBuilder();
                recieve();
            }

            return m_socket.Connected;
        }

        

        /// <summary>
        /// Disconnect from the server, point socket to null
        /// </summary>
        /// <returns>true if disconnect was successful</returns>
        public bool disconnect() {
            if (m_socket != null) {
                if (m_socket.Connected)
                    m_socket.Disconnect(false);
                m_socket = null;
            }
            return m_socket == null;
        }

        /// <summary>
        /// Send data to the server
        /// </summary>
        /// <param name="command">command to send to the server</param>
        public void send(string command) {
            byte[] sendBuffer = Settings.DEFAULT_ENCODING.GetBytes(command + "\r\n"); //Appending the \r\n so you dont have to
            try {
                m_socket.BeginSend(sendBuffer, 0, sendBuffer.Length, SocketFlags.None, onSendComplete, sendBuffer);
            } catch (Exception) {
                return;
            }
            return;
        }

        /// <summary>
        /// Recieve data from the server
        /// </summary>
        private void recieve() {
            byte[] recieveBuffer = new byte[512];

            try {
                m_socket.BeginReceive(recieveBuffer, 0, recieveBuffer.Length, SocketFlags.None, onRecvComplete, recieveBuffer);
            } catch (Exception) {
                disconnect();
                return;
            }
            return;
        }

        /// <summary>
        /// Async Send Complete, this doesnt do much.
        /// </summary>
        /// <param name="result">Async Results</param>
        private void onSendComplete(IAsyncResult result) {
            byte[] sendBuffer = (byte[])result.AsyncState;
			int sent = m_socket.EndSend(result);
			if (sent != sendBuffer.Length) {
				disconnect();
				throw new Exception("Send Failed!");
			}
            return;
        }

        /// <summary>
        /// Async Recieve Complete,
        /// This is a major driving method in this system.
        /// </summary>
        /// <param name="result">Async Result</param>
        private void onRecvComplete(IAsyncResult result) {
            int recv = 0;
            byte[] recieveBuffer = (byte[])result.AsyncState;
            try {
                recv = m_socket.EndReceive(result);
            } catch (Exception) {
                disconnect();
                return;
            }
            if (recv == 0) return;

            m_response_buffer.Append(
                Settings.DEFAULT_ENCODING.GetString(recieveBuffer, 0, recv));

            foreach (string line in processResponseBuffer())
                Response(line); //Notify Client of response

            recieve(); //Set up recieve again
            return;
        }

        /// <summary>
        /// Alternative Way of handling messages
        /// </summary>
        /// <returns>array of responses</returns>
        /// <remarks>
        /// Handling this is more of a cached way, because it seems that the server
        /// sends messages continuously, so there are times when messages will come through, that
        /// look like garbage
        /// </remarks>
        private string[] processResponseBuffer() {
            List<string> responses = new List<string>();
            while (m_response_buffer.ToString().IndexOf("\r\n") != -1) {
                string buf = m_response_buffer.ToString();
                responses.Add(buf.Substring(0, buf.IndexOf("\r\n")));
                m_response_buffer.Remove(0,
                    buf.Substring(0, buf.IndexOf("\r\n")
                    ).Length + 2); //Remove message, plus the CRLF pair
            }
            return responses.ToArray();
        }

        /// <summary>
        /// Internal Trigger Handler
        /// </summary>
        /// <param name="response">response from server</param>
        /// <remarks>
        /// This is primarily here, simply to avoid a null reference exception, in the unlikely event that 
        /// that this event is not being subscribed to.
        /// </remarks>
        private void onResponse(string response) {
            return;
        }

        /// <summary>
        /// Create our socket 
        /// </summary>
        /// <returns>
        /// A <see cref="Socket"/>
        /// </returns>
        private Socket create_socket() {
            return new Socket(AddressFamily.InterNetwork,
                SocketType.Stream,
                ProtocolType.Tcp);
        }

        #region IDisposable Members

        /// <summary>
        /// Dispose Pattern
        /// </summary>
        public void Dispose() {
            disconnect();
            return;
        }

        #endregion
    }
}
